package com.tmt.jpafieldvalueconvert;

import com.tmt.annotation.parser.LazyLoadParser;
import com.tmt.helper.PackageHelper;
import com.tmt.helper.ReflectHelper;
import com.tmt.jpafieldvalueconvert.impl.DefaultMonitorLoader;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;

/**
 * 字段值变更监测器
 * Created by ThreeManTeam on 2017/5/22.
 */

public class JpaFieldValueChangeListener {

    private static HashMap<String, IJpaFieldValueMonitor> jpaFieldValueMonitorHashMap = new HashMap<>();

    public static MonitorLoader loader = null;

    @PostPersist
    public <T> void entityPrePersist(T obj) {
        onChange(obj, PostPersist.class);
    }

    @PostUpdate
    public <T> void entityPreUpdate(T obj) {
        onChange(obj, PostUpdate.class);
    }

    @PostRemove
    public <T> void entityPreRemove(T obj) {
        onChange(obj, PostRemove.class);
    }

    private <T> void onChange(T obj, Class eventCls) {
        switch (eventCls.getSimpleName()) {
            case "PostPersist":
            case "PostUpdate":
                List<Field> fields = ReflectHelper.findAllFieldByAnnotation(obj.getClass(), JpaFieldValueMonitor.class, -1);

                JpaFieldValueMonitor annotationOnClass = obj.getClass().getAnnotation(JpaFieldValueMonitor.class);
                if (CollectionUtils.isEmpty(fields) && obj.getClass().isAnnotationPresent(JpaFieldValueMonitor.class)) {
                    fields = ReflectHelper.findAllDeclaredFields(obj.getClass());
                }

                fields.forEach(x -> {
                    try {
                        JpaFieldValueMonitor annotation = x.getAnnotation(JpaFieldValueMonitor.class);
                        if (annotation == null)
                            annotation = annotationOnClass;

                        if (annotation == null)
                            return;

                        IJpaFieldValueMonitor iConverter = getMonitorInstance(obj, x.getName(), eventCls.getSimpleName(),
                                annotation.monitor(), getPackageName(obj, annotation));
                        if (iConverter != null)
                            iConverter.onChange(obj, x.getName(), eventCls.getSimpleName());
                    } catch (Exception ex) {
                    }
                });
                break;
            case "PostRemove":
                JpaFieldValueMonitor annotation = obj.getClass().getAnnotation(JpaFieldValueMonitor.class);
                if (annotation != null) {
                    try {
                        IJpaFieldValueMonitor iConverter = getMonitorInstance(obj, null, eventCls.getSimpleName(),
                                annotation.monitor(), getPackageName(obj, annotation));
                        if (iConverter != null)
                            iConverter.onChange(obj, null, eventCls.getSimpleName());
                    } catch (Exception ex) {
                    }
                }
                break;
        }
    }

    private static <T> String getPackageName(T obj, JpaFieldValueMonitor annotation) {
        String packageName = annotation.packageName();
        if (StringUtils.isEmpty(packageName)) {
            packageName = annotation.monitor().getPackage().getName();
            if (obj != null) {
                packageName = obj.getClass().getPackage().getName();
                int index = packageName.lastIndexOf('.');
                if (index > 0) {
                    packageName = packageName.substring(0, index);
                }
            }
        }
        return packageName;
    }

    private static <T, Y extends IJpaFieldValueMonitor> IJpaFieldValueMonitor getMonitorInstance(T obj, String propName, String eventName,
                                                                                                 Class<Y> monitor, String packageName) {
        IJpaFieldValueMonitor iMonitor = null;
        try {
            loader = DefaultMonitorLoader.getInstance();

            String key = LazyLoadParser.getValidSimpleClassName(obj.getClass()) + "." + propName + "." + eventName;
            if (jpaFieldValueMonitorHashMap.containsKey(key)) {
                iMonitor = jpaFieldValueMonitorHashMap.get(key);
                return iMonitor;
            }

            if (Modifier.isAbstract(monitor.getModifiers())) {
                List<String> clsNames = PackageHelper.getClassName(packageName, true, true, 1);
                for (String clsName : clsNames) {
                    Class cls = Class.forName(clsName);
                    if (monitor.isAssignableFrom(cls) && !Modifier.isAbstract(cls.getModifiers())) {
                        iMonitor = loader.loadMonitor(cls);
                        if (iMonitor.support(obj, propName, eventName)) {
                            jpaFieldValueMonitorHashMap.put(key, iMonitor);
                            break;
                        }
                    }
                }
            } else {
                iMonitor = loader.loadMonitor(monitor);
                if (iMonitor.support(obj, propName, eventName)) {
                    jpaFieldValueMonitorHashMap.put(key, iMonitor);
                } else {
                    iMonitor = null;
                }
            }

        } catch (Exception ex) {
        }
        return iMonitor;
    }

}
